package contentprovider.demo;

import android.app.Activity;
import android.os.Bundle;
/**
 * Android是如何实现应用程序之间数据共享的？
 * Content provider存储和检索数据并使得所有应用程序可以轻松的使用数据。这是应用程序之间分享数据的唯一途径.
 * 所有content provider实现了一套公共的接口来查询,添加,修改和删除数据. 而且Content providers是以类似数据库中
 * 表的方式将数据暴露。
 * 
 * 要想使应用程序的数据公开化，可通过2种方法：
 * 1. 创建一个属于你自己的Content provider
 * 2. 将你的数据添加到一个已经存在的Content provider中，前提是有相同数据类型并且有写入Content provider的权限。
 * 
 * ContentProvider 是如何向外界提供数据的？
 * Android提供了ContentProvider，一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去，
 * 而且ContentProviders是以类似数据库中表的方式将数据暴露，也就是说ContentProvider就像一个“数据库”。那么外
 * 界获取其提供的数据，也就应该与从数据库中获取数据的操作基本一样，只不过是采用URI来表示外界需要访问的“数据库”
 * 至于如何从URI中识别出外界需要的是哪个“数据库”
 * 
 * 在URI的D部分可能包含一个_ID ，这个应该出现在SQL语句中的，可以以种特殊的方式出现，这就要求我们在提供数据的
 * 时候，需要来额外关注这个特殊的信息。Android  SDK推荐的方法是：在提供数据表字段中包含一个ID，在创建表时
 * INTEGER PRIMARY KEY AUTOINCREMENT标识此ID字段。
 * 
 * 
 * ContentProvider 是如何组织数据的？
 * 组织数据主要包括：存储数据，读取数据，以数据库的方式暴露数据。数据的存储需要根据设计的需求，选择合适的存储结构，
 * 首选数据库，当然也可以选择本地其他文件，甚至可以是网络上的数据。数据的读取，以数据库的方式暴露数据这就要求，无
 * 论数据是如何存储的，数据最后必须以数据的方式访问。
 * 
 * 可能还有2个问题，是需要关注的。
 * 1. 	ContentProvider是什么时候创建的，是谁创建的？访问某个应用程序共享的数据，是否需要启动这个应用程序？这个问题在
 * 		Android SDK中没有明确说明，但是从数据共享的角度出发，ContentProvider应该是Android在系统启动时就创建了，否则就
 * 		谈不上数据共享了。这就要求在AndroidManifest.XML中使用<provider>元素明确定义。
 * 
 * 		(当一个查询初始化时，Android系统识别查询的目标content provider并确保它被启动并运行。系统会实例化所有ContentProvider对象
 * 		你不需要自己做这些。事实上你永远不会直接处理ContentProvider对象。通常，每种数据类型的ContentProvider对象只有一个实例。
 * 		但是可以与不同程序或进程中的多个ContentProvider交互。进程之间的相互作用是通过ContentProvider和ContentResolver类处理。)
 * 
 * 2. 	可能会有多个程序同时通过ContentResolver访问一个ContentProvider，会不会导致像数据库那样的“脏数据”？这个问题一
 * 		方面需要数据库访问的同步，尤其是数据写入的同步，在AndroidManifest.XML中定义ContentProvider的时候，需要考虑是
 * 		<provider>元素multiprocess属性的值；另外一方面Android在ContentResolver中提供了 notifyChange()接口，在数据改变
 * 		时会通知其他ContentObserver，这个地方应该使用了观察者模式，在 ContentResolver中应该有一些类似register，unregister的接口。

 * 
 * 如何通过一套标准及统一的接口获取其他应用程序暴露的数据？
 * Android提供了ContentResolver，外界的程序可以通过ContentResolver接口访问ContentProvider提供的数据。
 * 
 * 什么是URI？
 * 在学习如何获取ContentResolver前，有个名词是必须了解的：URI。URI是网络资源的定义，在Android中赋予其更广阔的含义。
 * 将其分为A，B，C，D 4个部分：
 * A：标准前缀，用来说明一个Content Provider来控制这些数据，无法改变的；
 * B：URI的标识，它定义了是哪个Content Provider提供这些数据。对于第三方应用程序，为了保证URI标识的唯一性，它必须是
 * 一个完整的、小写的  类名。这个标识在<provider> 元素的 authorities属性中说明：
 * <provider name=”.TransportationProvider”  authorities=”com.example.transportationprovider”  . . .  >
 * C：路径，Content Provider使用这些路径来确定当前需要生什么类型的数据，URI中可能不包括路径，也可能包括多个；
 * D：如果URI中包含，表示需要获取的记录的ID；如果没有ID，就表示返回全部；
 * 由于URI通常比较长，而且有时候容易出错，切难以理解。所以，在Android当中定义了一些辅助类，并且定义了一些常量
 * 来代替这些长字符串，例如：People.CONTENT_URI
 * 
 * ContentResolver介绍说明
 * ContentResolver是通过URI来查询ContentProvider中提供的数据。除了URI以外，还必须知道需要获取的数据段的名称，以及
 * 此数据段的数据类型。如果你需要获取一个特定的记录，你就必须知道当前记录的ID，也就是URI中D部分。
 * 
 * Content providers是以类似数据库中表的方式将数据暴露出去，那么ContentResolver也将采用类似数据库的操作来从Content providers
 * 中获取数据。现在简要介绍ContentResolver的主要接口，如下：
 * 返回值			函数声明
 * final Uri 		insert(Uri url, ContentValues values) ：插入一行数据到一个URI指定的表中.
 * final int 		delete(Uri url, String where, String[] selectionArgs) ：删除URI指定的行。
 * final Cursor 	query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) ：查询指定URI,返回结果集。
 * final int 		update(Uri uri, ContentValues values, String where, String[] selectionArgs)：更新URI指定的行。
 * 
 * 如何获取ContentResolver？
 * 你可以调用Activity或其他组件中实现的getContentResolver()方法来获得一个 ContentResolver，
 * 例如：ContentResolver cr = getContentResolver();
 * 
 * 其中ContentProvider负责
 * 		组织应用程序的数据；
 * 		向其他应用程序提供数据；
 * ContentResolver则负责
 * 		获取ContentProvider提供的数据；
 * 		修改/添加/删除更新数据等；
 * 
 * 【查询数据】
 * 
 * 查询一个content provider你需要3个信息：
 * 		标识provider的URI
 * 		要检索的数据字段的名称
 * 		这些字段的数据类型
 * 如果你要查询一个指定的记录，你需要这个记录的ID。
 * 
 * 你可以使用ContentResolver.query()或Activity.mangedQuery()方法来查询一个content provider。两个方法都有相同的形参且都返回一个
 * Cursor对象。然而mangedQuery()方法会导致activity控制Cursor的生命周期。一个被控制的Cursor处理所有细节，如当activity pause时卸
 * 载自己，当activity restart时重新查询。你可以调用Activity.startManagingCursor()让activity来控制一个未被控制的Cursor。
 * 为了将查询限制在一条记录，你可以将哪个记录的_ID值添加到URI，比如，如果ID是23，那么URI应该是：content://. . . ./23
 * 		(有一些辅助方法，特别是ContentUris.withAppendedId()和Uri.withAppendedPath()可以轻松将ID添加到URI. 返回添加了ID的URI对象)
 * 		Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);  
 * 		Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");  
 * 		Cursor cur = managedQuery(myPerson, null, null, null, null);
 * query()和mangedQuery()的其它参数来限定查询的细节，它们是：
 * 1. 	应该被return的数据列的名称。null值将返回所有列。否则，只有被列举了名称的列才被返回。所有来自于平台的content provider都为其
 * 		列声明了一个常量。比如：android.provider.Contacts.Phones 类为电话表的类的名称声明的常量有：_ID, NUMBER, NUMBER_KEY, 
 * 		NAME, 等。
 * 2.	 一个决定哪行被返回的flter。它是用SQL的WHERE从句的格式,null值将返回所有的行（除非URI限制只查询一条记录）。 
 * 3.	可供选择的形参
 * 4.  返回的行的排列顺序，它是用SQL的ORDER BY从句的格式（不包含ORDER BY）。Null将按照其在表中的默认顺序返回。
 * 
 * 【修改数据】
 * 所有的修改操作都是通过ContentResolver的方法完成。比起读数据一些content provider需要更为严格的权限来写数据。如果你没有权限来
 * 写入content provider，ContentResolver的方法将失败。
 * 
 * 添加记录：
 * ContentValues values = new ContentValues(); 
 * values.put(People.NAME, "Abraham Lincoln"); 
 * values.put(People.STARRED, 1); 
 * Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
 * 
 * 添加新的值：
 * 一旦一个记录存在，你就可以向其添加新的信息或者修改存在的信息。比如说，上例的下一步将是向新的条目添加联系信息---如一个电话号码
 * 或者一个IM或者是e-mail地址。在Contact数据库里添加记录的最好的方法，就是将要添加新数据的表的名字附加在URI上，然后用这个修正
 * 的URI来增加新的数据值每一个联系人表为了这个目的将给出一个 作为CONTENT_DIRECTORY 常量的名字
 * 
 *     Uri phoneUri = null; 
 *     Uri emailUri = null;
 *     为Abraham Lincoln添加一个电话号码.  先从由insert()返回的新纪录的URI开始，它以新纪录的_ID结尾。所以我们不需要自己添加ID。
 *     并用返回的 URI 来插入一个电话号码  
 *     phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);  
 *     values.clear();  
 *     values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE);  
 *     values.put(People.Phones.NUMBER, "1233214567");  
 *     getContentResolver().insert(phoneUri, values);  
 *     
 *     用相同的方法添加 email 地址.  
 *     emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);  
 *     values.clear();  
 *     values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL);  
 *     values.put(People.ContactMethods.DATA, "test@example.com");  
 *     values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME);  
 *     getContentResolver().insert(emailUri, values);   
 *     
 * 你可以调用ContentValues.put()(形参有一个byte数组)将少量的小的二进制数据放入表中。如一个图标式的图像或一小段音频可以用此方式。
 * 然而如果你有大量的二进制数据要添加，比如一幅照片或者一首完整的歌，应在表格中放置一个该数据的content：URI并调用
 * ContentResolver.openOutputStream()（形参为文件的URI）。（这将使content provider将数据存入文件并在记录的一个隐藏字段记录文
 * 件的路径）。
 * 
 * 	在这一点，MediaStore content provider（分配image, audio和 video 数据的主要provider）利用了一个特殊的协议：与query()和
 * managequery()一起使用来获得二进制数据的元信息（比如，照片的标题或者拍摄的日期等等）的URI，与openInputStream()一起使用就可
 * 以来获得它自身的数据。同样的，与insert()一起使用来将元信息存入MediaStore记录的URI，也被用来与openOutputStream()一起来放置
 * 那里的二进制数据。下面的程序说明了这个过程：
 *  
 *  // 在 ContentValues map中保存图像的名称和描述   
 *  ContentValues values = new ContentValues(3);  
 *  values.put(Media.DISPLAY_NAME, "road_trip_1");  
 *  values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");  
 *  values.put(Media.MIME_TYPE, "image/jpeg");  
 *  //添加一个新的记录不是用bitmap，而是用刚才设置的值.  
 *  // insert() 返回新纪录的 URI.  
 *  Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);  
 *  // 现在获得一个该记录的文件的句柄,将数据存入.  
 *  // 这里, sourceBitmap 是一个 Bitmap 对象,它代表了要存如数据库的文件.  
 *  try {  
 *      OutputStream outStream = getContentResolver().openOutputStream(uri);  
 *     sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);  
 *    outStream.close();  
 *  } catch (Exception e) {  
 *  Log.e(TAG, "exception while writing image", e);  
 *  } 
 *  
 *  批量修改记录：
 *  要批量修改一组记录的数据（如将所有字段中的”NY ”修改为”New York”），调用函数ContentResolver.update()，传入列以及要修改的值。
 *  
 *  删除记录：
 *  要删除一条记录，调用ContentResolver.delete()，并传入指定行的Uri
 *  要删除多行，调用ContentResolver.delete()并传入要删除的这一类记录的Uri(比如android.provider.Contacts.People.CONTENT_URI)和
 *  一个SQL WHERE形式的语句来指明哪行要删除。
 *  
 *  创建一个Content Provider
 *  要创建一个content provider你应该：
 *  1. 建立一个存储数据的系统。多数的content provider用Android的文件存储方法或SQlite数据库来存储数据，但是你可以用任何你想用的方
 *  	法存储。Android提供了SQLiteOpenHelper类来帮助你创建一个数据库以及SQLiteDatabase来管理它。
 *  2.	继承ContentProvider类来提供对数据的访问
 *  3.	在manifest文件（AndroidManifest.xml）中为你的应用程序声明这个content provider
 *  
 *  继承ContentProvider类
 *  要实现6个ContentProvider类中声明的抽象方法：query() 、insert()  、update()  、delete()  、getType()  、onCreate() 。
 *  
 *  query() 方法必须返回一个可以遍历请求的数据的Cursor对象。Cursor本身是个接口，但是Android提供了一些你可使用的现成的Cursor对象。
 *  比如说，SQLiteCursor可以在SQLite数据库中存储的数据上迭代。你可以通过SQLiteDatabase类的query()方法获得该Cursor。还有其他的
 *  Cursor实现—如为没有存储在数据库中的数据准备的MatrixCursor。
 *  
 *  因为这些ContentProvider方法可以在不同的进程和线程中使用各种ContentResolver对象调用，所以它们必须被实现为线程安全的。
 *  
 *  当要数据改变时，你也许同样会调用ContentResolver.notifyChange()来通知listener。
 *  
 *  在定义子类之后，还有其它的步骤：
 *  
 *  1.	定义一个public static final Uri CONTENT_URI。这是一个代表你的content provider持有的全content: URI的字符串，你必须给其定义
 *  	一个唯一的值。
 *  
 *  	最好的方法是使用content provider的fully-qualified类名（小写）。比如，一个TransportationProvider类的URI可以这样定义：
 * 	 	public static final Uri CONTENT_URI = Uri.parse("content://com.example.codelab.transportationprovider"); 
 * 
 * 		如果这个provider还有子表，也要为每个子表定义CONTENT_URI常量。这些常量必须有相同的authority，只有path不同，比如：
 * 		content://com.example.codelab.transportationprovider/train 
 * 		content://com.example.codelab.transportationprovider/air/domestic 
 * 		content://com.example.codelab.transportationprovider/air/international
 * 
 * 2.	定义content provider将要返回给用户的列的名字。如果你使用了底层的数据库，这些列名通常与SQL数据库列名相同。同样是声明为
 * 		public static字符串常量，用户可以使用它们来指明要查询的列。确保要提供一个整型的列，列名为"_id"(常量为_ID)作为记录的ID。
 * 		必须有这个字段。如果你使用SQLite数据库，_ID应该是下面的类型：
 * 
 * 		INTEGER PRIMARY KEY AUTOINCREMENT
 * 		AUTOINCREMENT是可选的，但是没有它，SQLIte增加一个ID计数器字段来记录最大记录数的下一个值（？）。如果你删除了最后的
 * 		一行，添加的下一行将会与删除的一行有相同的ID。AUTOINCREMENT 可以让SQLite来处理ID的增长。
 * 
 * 3.	备注好列的每一种数据类型，用户需要它来读取数据。
 * 
 * 4.	如果你要处理一个新的数据类型，你必须声明一个新的MIME类型来作为你实现ContentProvider.getType()的返回。这个类型部分取决
 * 		于提交给getType()的content:URI是否限制对具体记录的请求。对单个记录有一种形式，对多个记录又有另一种形式。用Uri的方法来确
 * 		定什么将被请求。下面是每种类型的一般格式：
 * 
 * 		对单个记录：  vnd.android.cursor.item/vnd.yourcompanyname.contenttype
 * 		例如：请求列车记录122有这样的ＵＲＩ：content://com.example.transportationprovider/trains/122
 * 		会返回这个MIME类型：vnd.android.cursor.item/vnd.example.rail
 * 
 * 		对多个记录：vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
 * 		例如：所有列车的请求URI：content://com.example.transportationprovider/trains
 * 		会返回这个MIME类型：vnd.android.cursor.dir/vnd.example.rail
 * 
 *  5.	如果你要公布因太大而不能放入表里的二进制数据，公布（expose）给用户的数据的的字段应该包含content:URI字符串。这是让用户可以
 *  	利用数据文件的字段。这个记录也应该有另一个字段，名为_data"来列出设备上那个文件的路径。这个字段不打算由用户读取，而是供
 *  	ContentReslover使用。用户只要在user-facing 的字段调用ContentResolver.openInputStream()方法。ContentResolver会请求"_data"
 *  	字段获得那条记录。因为它有比用户高的权限，所以它可以直接访问文件并为用户返回读取的结果。
 *  
 *  
 *  声明content provider
 *  要让Android系统知道你开发的content provider，你应该在AndroidManifest.xml文件中用 <provider> 元素声明。未声明的
 *  content provider不可用。<provider>元素的name属性是ContentProvider子类的全名。authorities 属性是标识provider的URI中的
 *  authority部分。比如，ContentProvider子类. 如果是AutoInfoProvider，<provider> 元素或许如下：
 *      <provider android:name="com.example.autos.AutoInfoProvider"  
 *                  android:authorities="com.example.autos.autoinfoprovider"   
 *                  . . . />  
 *       </provider>  
 *       
 *	注意authorities 属性省略了content:URI的path部分。比如说，如果AutoInfoProvider控制着不同类型汽车或不同生产厂商的表格：
 *       content://com.example.autos.autoinfoprovider/honda 
 *       content://com.example.autos.autoinfoprovider/gm/compact 
 *       content://com.example.autos.autoinfoprovider/gm/suv
 *	它们的path不会在manifest中声明。Authority标识这个provider，而不是path；你的provider可以以你选的的任何方式来解释（interpret）
 *	URI的path部分。其他的< provider >属性可以设置读写数据的权限，为其提供一个图标或者文本来显示给用户，使provider可用或不可用
 *	等等。如果数据不需要在多个运行版本的content provider同步,将multiprocess 属性设置为"true"。这会允许在每个客户端进程创建一个
 *	provider的实例，消除了执行IPC的需要。（IPC：进程间通信）
 *
 *	Content URI 总结：这里是一个content URI的重要部分的概括：
 *	A.        	标准的前缀，指示数据由content provider控制。它永远不会被修改
 *	B.        	URI的authority 部分，它标识这个content provider。对于第三方应用程序，这一部分应该是一个fully-qualified类名（小写）
 *				来确保唯一。Authority在< provider >元素的authorities 属性声明。
 *	   <provider android:name=".TransportationProvider"  android:authorities="com.example.transportationprovider" />  
 */

public class ContentProviderActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}